-- comradio2 client again local TweenService = game:GetService("TweenService") local MessagingService = game:GetService("MessagingService") local HttpService = game:GetService("HttpService") local TextService = game:GetService("TextService") local Players = game:GetService("Players") local RunService = game:GetService("RunService") local part = Instance.new("SpawnLocation") local gui = Instance.new("SurfaceGui") local frame = Instance.new("ScrollingFrame") local list = Instance.new("UIListLayout") local owner: Player = owner local channels: Array = {} local doingRosters = {} local nickname = owner.DisplayName gui.SizingMode = Enum.SurfaceGuiSizingMode.PixelsPerStud gui.PixelsPerStud = 100 gui.Face = Enum.NormalId.Back gui.Adornee = part frame.BackgroundTransparency = 1 frame.AutomaticCanvasSize = Enum.AutomaticSize.XY frame.Size = UDim2.fromScale(1, 1) frame.BorderSizePixel = 0 list.FillDirection = Enum.FillDirection.Vertical list.HorizontalAlignment = Enum.HorizontalAlignment.Left list.SortOrder = Enum.SortOrder.LayoutOrder part.Enabled = false part.CanTouch = false part.CanCollide = false part.Locked = true part.Anchored = true part.Material = Enum.Material.Glass part.Transparency = 0.75 part.Size = Vector3.new(10, 6, 0.01) part.Color = Color3.new(0.5, 0.34, 0.4) part.Parent = script list.Parent = frame frame.Parent = gui gui.Parent = script local function createText(text: string) local box = Instance.new("TextBox") box.BackgroundTransparency = 1 box.Font = Enum.Font.Code box.TextColor3 = Color3.new(1, 1, 1) box.TextXAlignment = Enum.TextXAlignment.Left box.TextYAlignment = Enum.TextYAlignment.Top box.TextSize = 20 box.AutomaticSize = Enum.AutomaticSize.XY box.TextWrapped = true box.ClipsDescendants = false box.BorderSizePixel = 0 box.Text = typeof(text) == "string" and text or tostring(text) box.RichText = true box.Name = tostring(os.clock()) box.Parent = frame if #frame:GetChildren() > 30 then local oldest: TextBox for _, v in pairs(frame:GetChildren()) do if v:IsA("TextBox") then if not oldest then oldest = v elseif tonumber(oldest.Name) > tonumber(v.Name) then oldest = v end end end oldest:Destroy() end return box end local function setCFrame() local cf = owner.Character and owner.Character:FindFirstChild("HumanoidRootPart") and owner.Character.HumanoidRootPart.CFrame part.CFrame = cf * CFrame.new(0, 4, -4) end local function stepped() part.Anchored = true local tween = TweenService:Create(frame, TweenInfo.new(0.25), { CanvasPosition = Vector2.new(0, list.AbsoluteContentSize.Y - 125), }) tween:Play() end type MessagingServiceInput = { Data: any, Sent: number, } type MessageStruct = { Author: number, Content: string, Comment: string?, Type: string, Nickname: string, } local function filterRichText(...) local array = { ... } local result = {} for _, v in pairs(array) do result[#result + 1] = typeof(v) == "number" and v or string.gsub( string.gsub( string.gsub(string.gsub(string.gsub(v, "&", "&"), "<", "<"), '"', """), "'", "'" ), ">", ">" ) end return unpack(result) end local function getUsername(uid: number) local username = nil pcall(function() username = Players:GetNameFromUserIdAsync(uid) end) return username end local function filterFromUser(s: string) return TextService:FilterStringAsync(s, owner.UserId, Enum.TextFilterContext.PublicChat) :GetChatForUserAsync(owner.UserId) end local function send(channelName: string, data: MessageStruct) local alive, error task.spawn(function() repeat alive, error = pcall(function() MessagingService:PublishAsync(string.format("comradio:%s", channelName), HttpService:JSONEncode(data)) end) if not alive then task.wait(0.25) end until alive end) task.spawn(function() task.wait(1) if not alive then local sinceAlive = os.clock() local box = createText( string.format( "[%s] %s", "system", string.format( "Failed sending message; retrying every 0.25s. (%i seconds failed)", os.clock() - sinceAlive ) ) ) repeat box.Text = string.format( "[%s] %s", "system", string.format( "Failed sending message; retrying every 0.25s. (%i seconds failed)", os.clock() - sinceAlive ) ) task.wait(0.25) until alive box:Destroy() end end) end local function getRealChannel(name: string) return name == "default" and "" or name end local function getChannelName(name: string) return name == "" and "default" or name end local doingRoster = false local rosterResults = {} local function parseMessage(name: string, message: MessagingServiceInput) local alive, json: MessageStruct = pcall(HttpService.JSONDecode, HttpService, message.Data) if not alive then return end local author, text, type, comment, nick2 = filterRichText(json.Author, json.Content, json.Type, json.Comment, json.Nickname) if not author or not name or not message or not type then return end if typeof(author) ~= "number" then return end if typeof(text) ~= "string" then return end if typeof(type) ~= "string" then return end comment = comment or "" local nick = nick2 and filterFromUser(nick2) local username = getUsername(author) if not username then return end local fullUsername = string.format("%s%s", nick or username, nick and " <" .. username .. ">" or "") if type == "text" then createText( string.format("[%s] %s: %s", name, fullUsername, filterFromUser(text)) ) elseif type == "welcome" then createText(string.format("[%s] Welcome back %s!", name, fullUsername, author)) elseif type == "status" then createText( string.format( "[%s] %s has changed their status to %s.", name, fullUsername, filterFromUser(comment) ) ) elseif type == "ping" then createText( string.format( "[%s] %s: @%s %s", name, fullUsername, filterRichText(getUsername(text) or ""), filterFromUser(comment) ) ) elseif type == "sound" then -- we want the box local soundId = text local box = createText( string.format( "[%s] %s: %s [%s]", name, username, comment, text ) ) local button = Instance.new("TextButton") local sound = Instance.new("Sound") local playing = false button.BorderSizePixel = 0 button.Position = UDim2.fromScale(1, 0) + UDim2.fromScale(0.02, 0) button.Size = UDim2.fromOffset(25, 25) button.BackgroundColor3 = Color3.new(0.1, 0.51, 0.98) button.Text = "▶" button.TextScaled = true button.Parent = box sound.SoundId = json.Content sound.Volume = 1 sound.Looped = true sound.Parent = part button.MouseButton1Click:Connect(function() playing = not playing if playing then sound:Play() button.Text = "⏸" else sound:Pause() button.Text = "▶" end end) elseif type == "image" then -- we want the box local box = createText( string.format( "[%s] %s: %s [%s]", name, username, comment, text ) ) local image = Instance.new("ImageLabel") image.BackgroundTransparency = 1 image.Size = UDim2.fromOffset(100, 100) image.Position = UDim2.fromOffset(0, TextService:GetTextSize(box.ContentText, box.TextSize, box.Font, box.AbsoluteSize).Y) image.ScaleType = Enum.ScaleType.Fit image.Image = json.Content image.Parent = box elseif type == "rosterResponse" then if doingRoster then rosterResults[tostring(author)] = fullUsername end elseif type == "rosterRequest" then if doingRosters[tostring(author)] then return end doingRosters[tostring(author)] = true createText( string.format( "[%s] %s has requested a roster, responding.", name, fullUsername, author ) ) send(getRealChannel(name), { Type = "rosterResponse", Author = owner.UserId, Content = "", Comment = "", }) task.delay(5, function() doingRosters[tostring(author)] = false end) else print("unknown type:", type) end end local talkingChannel = "default" local function connectToChannel(name: string) local ralName = getChannelName(name) send(name, { Type = "welcome", Content = "", Comment = "", Author = owner.UserId, Nickname = nickname, }) channels[name] = MessagingService:SubscribeAsync(string.format("comradio:%s", name), function(...) parseMessage(ralName, ...) end) createText( string.format( "[system] Connected to %s!", getChannelName(name) ) ) end local function disconnectFromChannel(name: string) if channels[name] then createText( string.format( "[system] Disconnected from %s!", getChannelName(name) ) ) channels[name]:Disconnect() channels[name] = nil end end local function onMessageIn(msg: string) if string.sub(msg, 1, 1) == "#" then local split = string.split(msg, " ") if split[1] == "#listen" then connectToChannel(getRealChannel(split[2])) end if split[1] == "#mute" then disconnectFromChannel(getRealChannel(split[2])) end if split[1] == "#bring" then setCFrame() end if split[1] == "#nick" then nickname = table.concat(split, " ", 2) end if split[1] == "#ping" then send(getRealChannel(talkingChannel), { Comment = table.concat(split, " ", 3), Author = owner.UserId, Content = split[2], Type = "ping", Nickname = nickname, }) end if split[1] == "#list" then local s = {} for channel, _ in pairs(channels) do table.insert(s, string.format("%s", getChannelName(channel))) end createText( string.format("[system] Listening to channels:\n%s", table.concat(s, "\n")) ) end if split[1] == "#ts" then talkingChannel = split[2] createText( string.format( "[system] Switched speaking channel to %s.", filterRichText(split[2]) ) ) end if split[1] == "#sound" or split[1] == "#image" then local content = split[2] local id = string.match(content, "%d+") local toSend = "" if split[1] == "#image" then toSend = (id and "rbxthumb://type=Asset&id=" .. id .. "&w=420&h=420") or content elseif split[1] == "#sound" then toSend = id and "rbxassetid://" .. id or content end send(getRealChannel(talkingChannel), { Author = owner.UserId, Comment = table.concat(split, " ", 3), Content = toSend, Type = string.sub(split[1], 2, -1), Nickname = nickname, }) end if split[1] == "#clear" then for _, v in pairs(frame:GetChildren()) do if v:IsA("TextBox") then v:Destroy() end end end if split[1] == "#status" then send(getRealChannel(talkingChannel), { Author = owner.UserId, Comment = table.concat(split, " ", 2), Content = "", Type = "status", Nickname = nickname, }) end if split[1] == "#roster" then doingRoster = true send(getRealChannel(talkingChannel), { Author = owner.UserId, Content = "", Comment = "", Type = "rosterRequest", Nickname = nickname, }) local start = os.time() local box = createText( string.format("[system] Roster %s! Roster results:\n", "in progress") ) task.spawn(function() -- j????!?!?!? while task.wait() do local results = {} for author, username in pairs(rosterResults) do table.insert( results, string.format("%s (%i)", username, author) ) end local done = false if os.time() - start >= 5 then done = true end box.Text = string.format( "[system] Roster %s! Roster results:\n%s", done and "finished" or "in progress" .. string.format(" (%i seconds left)", 5 - (os.time() - start)), table.concat(results, "\n") ) if done then break end end rosterResults = {} doingRoster = false end) end return true end end setCFrame() createText(string.format("Welcome back, %s! Press ; to toggle the message send UI.", owner.Name)) connectToChannel(getRealChannel("default")) RunService.Stepped:Connect(stepped) local remote = Instance.new("RemoteEvent") local screenGui = Instance.new("ScreenGui") local mainFrame = Instance.new("Frame") local textBox = Instance.new("TextBox") textBox.BackgroundTransparency = 1 textBox.TextXAlignment = Enum.TextXAlignment.Left textBox.TextYAlignment = Enum.TextYAlignment.Top textBox.Font = Enum.Font.Code textBox.TextScaled = true textBox.Name = "text_box" textBox.Text = "" textBox.ClearTextOnFocus = false textBox.TextColor3 = Color3.new(1, 1, 1) textBox.Size = UDim2.fromScale(1, 1) textBox.Parent = mainFrame mainFrame.Name = "__main" mainFrame.BackgroundColor3 = Color3.new(0.25, 0.25, 0.25) mainFrame.BorderSizePixel = 0 mainFrame.Size = UDim2.fromScale(0.95, 0.05) mainFrame.Position = UDim2.fromScale(0.025, 0.85) mainFrame.Parent = screenGui screenGui.Name = "gui" remote.OnServerEvent:Connect(function(player: Player, message: string?) if player ~= owner then return end if message and typeof(message) == "string" and message ~= "" then local x = onMessageIn(message) if x then return end send(getRealChannel(talkingChannel), { Author = owner.UserId, Comment = "", Content = message, Type = "text", Nickname = nickname, }) end end) screenGui.Parent = remote remote.Parent = owner.PlayerGui NLS( [=[ local remote = script.Parent local gui = remote:FindFirstChild("gui") local frame = gui and gui:FindFirstChild("__main") local textBox = frame and frame:FindFirstChild("text_box") frame.Visible = false assert(textBox, "textbox not found :(") textBox.FocusLost:Connect(function(e) if e then remote:FireServer(textBox.Text) textBox.Text = "" frame.Visible = false end end) game:GetService("UserInputService").InputEnded:Connect(function(e,g) if g then return end if e.KeyCode == Enum.KeyCode.Semicolon then frame.Visible = true textBox:CaptureFocus() end end) ]=], remote )